/*
 * Copyright (c) 1997, 1998, 2003, 2006 Aleksandar Samardzic
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <assert.h>		/* for assert() function */
#include <math.h>		/* for sqrt() function */
#include <stdlib.h>		/* for malloc() function */
#include <string.h>		/* for memcpy() function */
#include "scene_builder.h"
#include "polygon.h"
#include "triangle.h"
#include "sphere.h"

/* SceneBuilder_Init - SceneBuilder constructor */
void
SceneBuilder_Init(this, pScene)
     PSceneBuilder  *this;
     PScene         *pScene;
{
	this->pScene = pScene;

	/* initialize current material */
	this->pCurrMaterial = malloc(sizeof(PMaterial));
	assert(this->pCurrMaterial != NULL);
	memset(this->pCurrMaterial, 0, sizeof(PMaterial));
	List_AddItem(&(pScene->materials), this->pCurrMaterial);

	/* viewpoint not specified yet */
	this->viewpointBuilt = FALSE;
}

/* SceneBuilder_BackgroundColor - set scene background color */
void
SceneBuilder_BackgroundColor(this, pNFFBackgroundColor)
     PSceneBuilder  *this;
     NFFBackgroundColor *pNFFBackgroundColor;
{
	PScene         *pScene = this->pScene;

	/* set color */
	pScene->bckgColor[RED] = pNFFBackgroundColor->r;
	pScene->bckgColor[GREEN] = pNFFBackgroundColor->g;
	pScene->bckgColor[BLUE] = pNFFBackgroundColor->b;
}

/* SceneBuilder_CylCone - display warning, since cylinder/cone primitives
 * are not supported */
void
SceneBuilder_CylCone(this, pCylCone)
     PSceneBuilder  *this;
     NFFCylCone     *pCylCone;
{
	fprintf(stderr,
		"Warning: ignoring cylinder/cone primitive (unsupported).\n");
}

/* SceneBuilder_Light - create a light and add it to list of lights */
void
SceneBuilder_Light(this, pNFFLight)
     PSceneBuilder  *this;
     NFFLight       *pNFFLight;
{
	PScene         *pScene = this->pScene;
	PLight         *pLight;

	pLight = malloc(sizeof(PLight));
	assert(pLight != NULL);

	/* set position */
	pLight->position[X] = pNFFLight->position.x;
	pLight->position[Y] = pNFFLight->position.y;
	pLight->position[Z] = pNFFLight->position.z;

	/* set color */
	pLight->color[RED] = pNFFLight->color.r;
	pLight->color[GREEN] = pNFFLight->color.g;
	pLight->color[BLUE] = pNFFLight->color.b;

	List_AddItem(&(pScene->lights), pLight);
}

/* SceneBuilder_Material - create a material, add it to list of materials
 * and set current material to it */
void
SceneBuilder_Material(this, pNFFMaterial)
     PSceneBuilder  *this;
     NFFMaterial    *pNFFMaterial;
{
	PMaterial      *pMaterial;

	pMaterial = this->pCurrMaterial = malloc(sizeof(PMaterial));
	assert(pMaterial != NULL);

	pMaterial->ka = 0;	/* set ambient coefficient */
	pMaterial->kd = pNFFMaterial->Kd;	/* set diffuse coefficient 
						 */
	pMaterial->ks = pNFFMaterial->Ks;	/* set specular
						 * coefficient */
	pMaterial->n = pNFFMaterial->Shine;	/* set Phong power */

	/* set color */
	pMaterial->color[RED] = pNFFMaterial->color.r;
	pMaterial->color[GREEN] = pNFFMaterial->color.g;
	pMaterial->color[BLUE] = pNFFMaterial->color.b;

	pMaterial->kr = pNFFMaterial->Ks;	/* set reflection
						 * coefficient */
	pMaterial->kt = pNFFMaterial->T;	/* set refraction
						 * coefficient */
	pMaterial->ir = pNFFMaterial->index_of_refraction;	/* set
								 * index
								 * of
								 * refraction 
								 */

	/* add material to list of materials */
	List_AddItem(&(this->pScene->materials), pMaterial);
}

/* SceneBuilder_Polygon - create a polygon and add it to list of
 * primitives */
void
SceneBuilder_Polygon(this, pNFFPolygon)
     PSceneBuilder  *this;
     NFFPolygon     *pNFFPolygon;
{
	PScene         *pScene = this->pScene;
	PPolygon       *pPolygon;
	PTriangle      *pTriangle;
	DWord           numVertices,
	                index;
	NFFPoint       *pNFFPoint;
	PVertex        *pVertex;
	PVector         normal;

	if ((numVertices = pNFFPolygon->numVertices) == 3) {	/* create
								 * a
								 * triangle 
								 */
		pTriangle = malloc(sizeof(PTriangle));
		assert(pTriangle != NULL);
		TRIANGLE_DECLAREDYNAMIC(*pTriangle)
		    List_AddItem(&(pScene->objects), pTriangle);

		/* apply current material */
		pTriangle->pMaterial = this->pCurrMaterial;

		/* set vertices */
		for (index = 0, pNFFPoint = pNFFPolygon->pVertices;
		     index < numVertices; index++, pNFFPoint++) {
			pTriangle->vertex[index].point[X] = pNFFPoint->x;
			pTriangle->vertex[index].point[Y] = pNFFPoint->y;
			pTriangle->vertex[index].point[Z] = pNFFPoint->z;
		}

		/* calculate triangle plane coefficients */
		Plane_CalcCoeffs(&(pTriangle->plane),
				 pTriangle->vertex[0].point,
				 pTriangle->vertex[1].point,
				 pTriangle->vertex[2].point);
		normal[X] = pTriangle->plane.A, normal[Y] =
		    pTriangle->plane.B, normal[Z] = pTriangle->plane.C;

		/* set normals */
		Vector_Assign(pTriangle->vertex[0].normal, normal);
		Vector_Assign(pTriangle->vertex[1].normal, normal);
		Vector_Assign(pTriangle->vertex[2].normal, normal);

		/* calculate triangle additional coefficients */
		Triangle_CalcCoeffs(pTriangle);
	} else {		/* create a polygon */

		pPolygon = malloc(sizeof(PPolygon));
		assert(pPolygon != NULL);
		POLYGON_DECLAREDYNAMIC(*pPolygon)
		    List_AddItem(&(pScene->objects), pPolygon);

		/* apply current material */
		pPolygon->pMaterial = this->pCurrMaterial;

		/* set vertices */
		Polygon_Init(pPolygon, numVertices);
		for (index = 0, pVertex = pPolygon->vertex, pNFFPoint =
		     pNFFPolygon->pVertices; index < numVertices;
		     index++, pVertex++, pNFFPoint++) {
			pVertex->point[X] = pNFFPoint->x;
			pVertex->point[Y] = pNFFPoint->y;
			pVertex->point[Z] = pNFFPoint->z;
		}

		/* calculate polygon plane coefficients */
		Plane_CalcCoeffs(&(pPolygon->plane),
				 pPolygon->vertex[0].point,
				 pPolygon->vertex[1].point,
				 pPolygon->vertex[2].point);
		normal[X] = pPolygon->plane.A, normal[Y] =
		    pPolygon->plane.B, normal[Z] = pPolygon->plane.C;

		/* set normals */
		for (index = 0, pVertex = pPolygon->vertex;
		     index < numVertices; index++, pVertex++)
			Vector_Assign(pVertex->normal, normal);
	}
}

/* SceneBuilder_PolygonalPatch - create a polygon and add it to list of
 * primitives */
void
SceneBuilder_PolygonalPatch(this, pNFFPolygonalPatch)
     PSceneBuilder  *this;
     NFFPolygonalPatch *pNFFPolygonalPatch;

{
	PScene         *pScene = this->pScene;
	PPolygon       *pPolygon;
	PTriangle      *pTriangle;
	DWord           numVertices,
	                index;
	NFFPoint       *pNFFPoint;
	NFFPoint       *pNFFNormal;
	PVertex        *pVertex;

	if ((numVertices = pNFFPolygonalPatch->numVertices) == 3) {	/* create 
									 * a 
									 * triangle 
									 */
		pTriangle = malloc(sizeof(PTriangle));
		assert(pTriangle != NULL);
		TRIANGLE_DECLAREDYNAMIC(*pTriangle)
		    List_AddItem(&(pScene->objects), pTriangle);

		/* apply current material */
		pTriangle->pMaterial = this->pCurrMaterial;

		/* set vertices and normals */
		for (index = 0, pNFFPoint =
		     pNFFPolygonalPatch->pVertices, pNFFNormal =
		     pNFFPolygonalPatch->pNormals; index < numVertices;
		     index++, pNFFPoint++, pNFFNormal++) {
			pTriangle->vertex[index].point[X] = pNFFPoint->x;
			pTriangle->vertex[index].point[Y] = pNFFPoint->y;
			pTriangle->vertex[index].point[Z] = pNFFPoint->z;
			pTriangle->vertex[index].normal[X] = pNFFNormal->x;
			pTriangle->vertex[index].normal[Y] = pNFFNormal->y;
			pTriangle->vertex[index].normal[Z] = pNFFNormal->z;
		}

		/* calculate triangle plane coefficients */
		Plane_CalcCoeffs(&(pTriangle->plane),
				 pTriangle->vertex[0].point,
				 pTriangle->vertex[1].point,
				 pTriangle->vertex[2].point);

		/* calculate triangle additional coefficients */
		Triangle_CalcCoeffs(pTriangle);
	} else {		/* create a polygon */

		pPolygon = malloc(sizeof(PPolygon));
		assert(pPolygon != NULL);
		POLYGON_DECLAREDYNAMIC(*pPolygon)
		    List_AddItem(&(pScene->objects), pPolygon);

		/* apply current material */
		pPolygon->pMaterial = this->pCurrMaterial;

		/* set vertices and normals */
		pPolygon->numVertices = numVertices;
		pPolygon->vertex = malloc(numVertices * sizeof(PVertex));
		assert(pPolygon->vertex != NULL);
		for (index = 0, pVertex = pPolygon->vertex, pNFFPoint =
		     pNFFPolygonalPatch->pVertices, pNFFNormal =
		     pNFFPolygonalPatch->pNormals; index < numVertices;
		     index++, pVertex++, pNFFPoint++, pNFFNormal++) {
			pVertex->point[X] = pNFFPoint->x;
			pVertex->point[Y] = pNFFPoint->y;
			pVertex->point[Z] = pNFFPoint->z;
			pVertex->normal[X] = pNFFNormal->x;
			pVertex->normal[Y] = pNFFNormal->y;
			pVertex->normal[Z] = pNFFNormal->z;
		}

		/* calculate polygon plane coefficients */
		Plane_CalcCoeffs(&(pPolygon->plane),
				 pPolygon->vertex[0].point,
				 pPolygon->vertex[1].point,
				 pPolygon->vertex[2].point);
	}
}

/* SceneBuilder_Sphere - create a sphere and add it to list of primitives */
void
SceneBuilder_Sphere(this, pNFFSphere)
     PSceneBuilder  *this;
     NFFSphere      *pNFFSphere;

{
	PScene         *pScene = this->pScene;
	PSphere        *pSphere;

	pSphere = malloc(sizeof(PSphere));
	assert(pSphere != NULL);
	SPHERE_DECLAREDYNAMIC(*pSphere)
	    List_AddItem(&(pScene->objects), pSphere);

	/* apply current material */
	pSphere->pMaterial = this->pCurrMaterial;

	/* set sphere center and radius */
	pSphere->center[X] = pNFFSphere->center.x;
	pSphere->center[Y] = pNFFSphere->center.y;
	pSphere->center[Z] = pNFFSphere->center.z;
	pSphere->radius = pNFFSphere->radius;
}

/* SceneBuilder_Viewpoint - set scene viewing specification */
void
SceneBuilder_Viewpoint(this, pNFFViewpoint)
     PSceneBuilder  *this;
     NFFViewpoint   *pNFFViewpoint;

{
	PScene         *pScene = this->pScene;
	PViewingParameters *pViewingParameters =
	    &(pScene->viewingParameters);
	double          mul,
	                den,
	                hither;
	PVector         u,
	                v;

	if (!this->viewpointBuilt) {	/* viewpoint not specified yet */
		this->viewpointBuilt = TRUE;

		/* set projection reference point */
		pViewingParameters->PRP[X] = pNFFViewpoint->from.x;
		pViewingParameters->PRP[Y] = pNFFViewpoint->from.y;
		pViewingParameters->PRP[Z] = pNFFViewpoint->from.z;

		/* set viewing reference point */
		pViewingParameters->VRP[X] = pNFFViewpoint->at.x;
		pViewingParameters->VRP[Y] = pNFFViewpoint->at.y;
		pViewingParameters->VRP[Z] = pNFFViewpoint->at.z;

		/* calculate viewing point normal */
		Vector_Sub(pViewingParameters->VPN,
			   pViewingParameters->VRP,
			   pViewingParameters->PRP);
		Vector_Normalize(pViewingParameters->VPN, mul, hither);

		/* set view-up vector */
		pViewingParameters->VUP[X] = pNFFViewpoint->up.x;
		pViewingParameters->VUP[Y] = pNFFViewpoint->up.y;
		pViewingParameters->VUP[Z] = pNFFViewpoint->up.z;
		Vector_Normalize(pViewingParameters->VUP, mul, den);

		/* calculate u and v vectors */
		Vector_CrossProduct(u, pViewingParameters->VPN,
				    pViewingParameters->VUP);
		Vector_Normalize(u, mul, den);
		Vector_CrossProduct(v, u, pViewingParameters->VPN);
		Vector_Normalize(v, mul, den);
		Vector_Assign(pViewingParameters->VUP, v);

		/* calculate viewing window dimensions */
		pViewingParameters->vmax =
		    hither * tan(pNFFViewpoint->angle / 2 * PI / 180);
		pViewingParameters->vmin = -pViewingParameters->vmax;
		pViewingParameters->umax =
		    pNFFViewpoint->aspect_ratio * pViewingParameters->vmax;
		pViewingParameters->umin =
		    pNFFViewpoint->aspect_ratio * pViewingParameters->vmin;

		/* set resolution */
		pScene->window.left = pScene->window.bottom = 0;
		pScene->window.width =
		    pNFFViewpoint->xres, pScene->window.height =
		    pNFFViewpoint->yres;
	}
}

/* SceneBuilder_PostBuild - postprocessing */
void
SceneBuilder_PostBuild(this)
     PSceneBuilder  *this;

{
	PScene         *pScene = this->pScene;
	double          ka;
	PPrimitive     *pPrimitive;
	PLight         *pLight;
	PListIterator   listIt;

	/* calculate ambient coefficient according to equation from SPD
	 * readme file */
	ka = sqrt(pScene->lights.numItems) / (2 * pScene->lights.numItems);

	/* apply ambient coefficient to primitives */
	for (ListIterator_Attach(&listIt, &(pScene->objects));
	     ListIterator_Next(&listIt, (void **) &pPrimitive);)
		pPrimitive->pMaterial->ka = ka;

	/* scale light intensities according to ambient coefficient */
	for (ListIterator_Attach(&listIt, &(pScene->lights));
	     ListIterator_Next(&listIt, (void **) &pLight);) {
		pLight->color[RED] *= ka;
		pLight->color[GREEN] *= ka;
		pLight->color[BLUE] *= ka;
	}
}
